#include <openssl/evp.h>
#include "nativecalls.h"

#ifdef _WINDOWS
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libcrypto.lib")
#pragma comment(lib, "crypt32.lib")
#endif // _WINDOWS

JNIEXPORT jlong JNICALL Java_catserver_server_network_NativeCalls_aesCreateEncryptKey(JNIEnv* env, jclass cls, jbyteArray keyJByteArray)
{
	EVP_CIPHER_CTX* ctx;
	int keyByteArrayLen = env->GetArrayLength(keyJByteArray);
	unsigned char* key = (unsigned char*)env->GetByteArrayElements(keyJByteArray, JNI_FALSE);
	if (!key)
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "GetByteArrayElements");
		return 0;
	}
	ctx = EVP_CIPHER_CTX_new();
	if (!ctx)
	{
		env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_CIPHER_CTX_new");
		return (jlong)0;
	}
	int createKeyReult = EVP_EncryptInit_ex(ctx, EVP_aes_128_cfb8(), NULL, key, key);
	EVP_CIPHER_CTX_set_padding(ctx, 0);
	if (createKeyReult != 1)
	{
		env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_EncryptInit_ex");
		return (jlong)createKeyReult;
	}
	env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
	return (jlong)ctx;
}

JNIEXPORT void JNICALL Java_catserver_server_network_NativeCalls_aesEncrypt(JNIEnv* env, jclass cls, jlong inAddr, jlong outAddr, jint len, jlong keyAddr)
{
	EVP_CIPHER_CTX* ctx = (EVP_CIPHER_CTX*)keyAddr;
	int outlen;
	if (1 != EVP_EncryptUpdate(ctx, (unsigned char*)outAddr, &outlen, (unsigned char*)inAddr, len))
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_EncryptUpdate");
		return;
	}
	if (1 != EVP_EncryptFinal_ex(ctx, (unsigned char*)outAddr + outlen, &outlen))
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_EncryptFinal_ex");
		return;
	}
}

JNIEXPORT void JNICALL Java_catserver_server_network_NativeCalls_aesReleaseEncryptKey(JNIEnv* env, jclass cls, jlong keyAddr)
{
	EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)keyAddr);
}

JNIEXPORT jlong JNICALL Java_catserver_server_network_NativeCalls_aesCreateDecryptKey(JNIEnv* env, jclass cls, jbyteArray keyJByteArray)
{
	EVP_CIPHER_CTX* ctx;
	int keyByteArrayLen = env->GetArrayLength(keyJByteArray);
	unsigned char* key = (unsigned char*)env->GetByteArrayElements(keyJByteArray, JNI_FALSE);
	if (!key)
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "GetByteArrayElements");
		return 0;
	}
	ctx = EVP_CIPHER_CTX_new();
	if (!ctx)
	{
		env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_CIPHER_CTX_new");
		return (jlong)0;
	}
	int createKeyReult = EVP_DecryptInit_ex(ctx, EVP_aes_128_cfb8(), NULL, key, key);
	EVP_CIPHER_CTX_set_padding(ctx, 0);
	if (createKeyReult != 1)
	{
		env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_DecryptInit_ex");
		return (jlong)createKeyReult;
	}
	env->ReleaseByteArrayElements(keyJByteArray, (jbyte*)key, JNI_FALSE);
	return (jlong)ctx;
}

JNIEXPORT void JNICALL Java_catserver_server_network_NativeCalls_aesDecrypt(JNIEnv* env, jclass cls, jlong inAddr, jlong outAddr, jint len, jlong keyAddr)
{
	EVP_CIPHER_CTX* ctx = (EVP_CIPHER_CTX*)keyAddr;
	int outlen;
	if (1 != EVP_DecryptUpdate(ctx, (unsigned char*)outAddr, &outlen, (unsigned char*)inAddr, len))
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_EncryptUpdate");
		return;
	}
	if (1 != EVP_DecryptFinal_ex(ctx, (unsigned char*)outAddr + outlen, &outlen))
	{
		env->ThrowNew(env->FindClass("java/lang/UnsupportedOperationException"), "EVP_EncryptFinal_ex");
		return;
	}
}

JNIEXPORT void JNICALL Java_catserver_server_network_NativeCalls_aesReleaseDecryptKey(JNIEnv* env, jclass cls, jlong keyAddr)
{
	EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)keyAddr);
}